
(function(){

  var namespace = 'App.Type';

  var nsEntity = Basis.Entity;
  var EntityType = nsEntity.EntityType;
  var EntitySetType = nsEntity.EntitySetType;

  var Dataset = Basis.Data.Dataset;

  // ====================================================
  // BEM functions
  //

  function idpart(rx, entityType){
    return function(retId){
      var id = this.getId();
      if (id)
      {
        var match = id.match(rx);
        var resId;

        if (match)
        {
          resId = match.length ? match[match.length - 1] : match[0];

          if (retId || !entityType)
            return resId;

          return entityType.get(resId);
        }
      }
    }
  }

  function split2parts(id){
    return (id || '').match(/^.+(?=\/)|\/[^_\.]*|__[^_\.]*|_[^\.]*|.+$/g);
  }
  function name2parts(name){
    return (name || '').match(/^[^_\.]*|__[^_\.]*|_[^\.]*|.+$/g);
  }

  //
  // resolve
  //

  var resolveRx = /\/([^_\.\/]*)(?:(__[^_\.]*)?(_[^\.]*)?)(\..+)?$/;
  var resolveTypes = [];
  function resolveBemType(path){
    var m = path.match(resolveRx);

    if (m)
      for (var i = 4; i > 0; i--)
        if (m[i])
          return resolveTypes[i];

    return Level;
  }

  function resolveBem(path){
    return bemIndex.get(path);
  }

  // ====================================================
  // BEM types
  //

  var BemName = new EntityType({
    name: 'BemName',
    fields: {
      name: nsEntity.StringId
    }
  });

  BemName.entityType.entityClass.extend({
    getParentId: function(){
      var parts = name2parts(this.getId());
      if (parts)
        return parts.slice(0, -1).join('');
    },
    getParent: function(){
      return BemName(this.getParentId() || null);
    }
  });

  //
  // TechType
  //
  var TechType = new EntityType({
    name: 'TechType',
    fields: {
      id: nsEntity.StringId,
      title: String,
      order: Number,
      template: String,
      isBinary: Boolean
    }
  });

  TechType.sorting = function(node){// Function.getter('target.getTechType().data.order');
    return TechType(node.target.getName()).data.order;
  }

  var bemIndex = new nsEntity.Index(nsEntity.StringId);

  //
  // Level
  //
  var Level = new EntityType({
    name: 'Level',
    index: bemIndex,
    fields: {
      id: nsEntity.StringId
    }
  });
  Level.entityType.entityClass.extend({
    getParent: Function.$null,
    getCmdPath: function(){
      return '-l ' + this.getName();
    },
    getName: function(){
      return this.getId();
    }
  });

  //
  // Tech
  //
  var Tech = new EntityType({
    name: 'Tech',
    index: bemIndex,
    fields: {
      id: nsEntity.StringId,  // {location}/{block_name}.{tech_name}
                              // {location}/{block_name}_{modificator_name}.{tech_name}
                              // {location}/{block_name}__{element_name}.{tech_name}
                              // {location}/{block_name}__{element_name}_{modificator_name}.{tech_name}
      type: String,
      lastPos: Number,
      content: String,
      mimeType: String,
      size: Number
    }
  });

  Tech.splitByOwner = new Dataset.Split({
    source: Tech.all,
    rule: function(tech){
      var id = tech.getId();
      if (id)
        return id.replace(/(\.[^\.]+)+$/, '');
    },
    subsetClass: {
      state: Basis.Data.STATE.READY
    }
  });
  
  //
  // Block
  //
  var Block = new EntityType({
    name: 'Block',
    index: bemIndex,
    fields: {
      id: nsEntity.StringId  // {location}/{block_name}
    }
  });

  //
  // Elem
  //
  var Elem = new EntityType({
    name: 'Elem',
    index: bemIndex,
    fields: {
      id: nsEntity.StringId  // {location}/{block_name}__{element_name}
    }
  });

  //
  // Mod
  //
  var Mod = new EntityType({
    name: 'Mod',
    index: bemIndex,
    fields: {
      id: nsEntity.StringId  // {location}/{block_name}_{modificator_name}
                             // {location}/{block_name}__{element_name}_{modificator_name}
    }
  });

  // extend with getters

  var bemPartGetters = {
    // name
    getFullName: idpart(/^(.+\/)*(.*)/),
    getName: function(){
      var id = this.getId();
      if (id)
        return split2parts(id).pop().replace(/^[\.\_\/]+/, '');
    },
    getCmdName: function(){
      return this.typeName.toLowerCase() + ' ' + this.getName();
    },

    // parent
    getParentId: function(){
      var parts = split2parts(this.getId());
      if (parts)
        return parts.slice(0, -1).join('');
    },
    getParent: function(){
      return resolveBem(this.getParentId());
    },

    // path
    getPath: idpart(/^(.+)(?=\/)/),
    getCmdPath: function(){
      var commandPath = '';
      var cursor = this;

      do
      {
        commandPath = ' -' + cursor.typeName.charAt(0).toLowerCase() + ' ' + cursor.getName() + commandPath;

        if (cursor.type == Block || cursor.type == Level)
          break;
      }
      while (cursor = cursor.getParent());

      return '-l ' + this.getPath() + commandPath;
    },

    // subsets
    getSubtree: function(){
      var result = [this];
      for (var i = 0, cursor; cursor = result[i++];)
      {
        var childs = bemSplit.getSubset(cursor);
        if (childs)
          result.push.apply(result, childs.getItems());
      }
      return result;
    },
    getAncestors: function(){
      var result = [];
      var cursor = this;

      do
      {
        result.push(cursor);
      }
      while (cursor = cursor.getParent());

      return result;
    },

    // methods
    rename: function(newName){
      var oldPath = this.getId();
      var parts = split2parts(oldPath);
      parts.push(parts.pop().match(/^[\/\_\.]+/)[0] + newName);
      var newPath = parts.join('');
      var childs = this.getSubtree();
      var rx = new RegExp('^' + oldPath.forRegExp());
      for (var i = 0, item; item = childs[i++];)
        item.set('id', item.getId().replace(rx, newPath));
    }/*,
    destroy: function(){
      this.constructor.prototype.destroy.call(this);
    }*/
  };

  [Block, Elem, Mod, Tech].forEach(function(type){
    type.entityType.entityClass.extend(bemPartGetters);
  });

  resolveTypes.push(Level, Block, Elem, Mod, Tech);

  var bemAll = new Dataset.Merge({
    sources: [
      Level.all,
      Block.all,
      Elem.all,
      Mod.all,
      Tech.all
    ]
  });
  var bemSplit = new Dataset.Split({
    source: bemAll,
    rule: function(object){
      return object.getParent();
    }
  });

  // ==============================================
  // Other types
  //


  // for console
  var CommandRun = new nsEntity.EntityType({
    name: 'CommandRun',
    fields: {
      text: nsEntity.StringId,
      lastRun: Number
    }
  });

  var CommandResult = new nsEntity.EntityType({
    name: 'CommandResult',
    fields: {
      id: nsEntity.IntId,
      type: function(value){
        return value || 'normal';
      },
      content: String
    }
  });

  //
  // export names
  //

  Basis.namespace(namespace).extend({
    split2parts: split2parts,
    name2parts: name2parts,
    resolveBem: resolveBem,
    resolveBemType: resolveBemType,

    bemAll: bemAll,
    bemSplit: bemSplit,

    BemName: BemName,
    TechType: TechType,
    Level: Level,
    Block: Block,
    Elem: Elem,
    Mod: Mod,
    Tech: Tech,

    CommandRun: CommandRun,
    CommandResult: CommandResult
  });

})();